#include "../TcpServer.hpp"
#include <fstream>
#include <ctype.h>
#include <sys/stat.h>
#include <regex>

#define DEFAULT_TIMEOUT 10

std::unordered_map<int, std::string> _statu_info =
{
    {100, "Continue"},
    {101, "Switching Protocol"},
    {102, "Processing"},
    {103, "Early Hints"},
    {200, "OK"},
    {201, "Created"},
    {202, "Accepted"},
    {203, "Non-Authoritative Information"},
    {204, "No Content"},
    {205, "Reset Content"},
    {206, "Partial Content"},
    {207, "Multi-Status"},
    {208, "Already Reported"},
    {226, "IM Used"},
    {300, "Multiple Choice"},
    {301, "Moved Permanently"},
    {302, "Found"},
    {303, "See Other"},
    {304, "Not Modified"},
    {305, "Use Proxy"},
    {306, "unused"},
    {307, "Temporary Redirect"},
    {308, "Permanent Redirect"},
    {400, "Bad Request"},
    {401, "Unauthorized"},
    {402, "Payment Required"},
    {403, "Forbidden"},
    {404, "Not Found"},
    {405, "Method Not Allowed"},
    {406, "Not Acceptable"},
    {407, "Proxy Authentication Required"},
    {408, "Request Timeout"},
    {409, "Conflict"},
    {410, "Gone"},
    {411, "Length Required"},
    {412, "Precondition Failed"},
    {413, "Payload Too Large"},
    {414, "URI Too Long"},
    {415, "Unsupported Media Type"},
    {416, "Range Not Satisfiable"},
    {417, "Expectation Failed"},
    {418, "I'm a teapot"},
    {421, "Misdirected Request"},
    {422, "Unprocessable Entity"},
    {423, "Locked"},
    {424, "Failed Dependency"},
    {425, "Too Early"},
    {426, "Upgrade Required"},
    {428, "Precondition Required"},
    {429, "Too Many Requests"},
    {431, "Request Header Fields Too Large"},
    {451, "Unavailable For Legal Reasons"},
    {501, "Not Implemented"},
    {502, "Bad Gateway"},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "HTTP Version Not Supported"},
    {506, "Variant Also Negotiates"},
    {507, "Insufficient Storage"},
    {508, "Loop Detected"},
    {510, "Not Extended"},
    {511, "Network Authentication Required"} 
};
std::unordered_map<std::string, std::string> _mime_info =
{
    {".aac",        "audio/aac"},
    {".abw",        "application/x-abiword"},
    {".arc",        "application/x-freearc"},
    {".avi",        "video/x-msvideo"},
    {".azw",        "application/vnd.amazon.ebook"},
    {".bin",        "application/octet-stream"},
    {".bmp",        "image/bmp"},
    {".bz",         "application/x-bzip"},
    {".bz2",        "application/x-bzip2"},
    {".csh",        "application/x-csh"},
    {".css",        "text/css"},
    {".csv",        "text/csv"},
    {".doc",        "application/msword"},
    {".docx",       "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
    {".eot",        "application/vnd.ms-fontobject"},
    {".epub",       "application/epub+zip"},
    {".gif",        "image/gif"},
    {".htm",        "text/html"},
    {".html",       "text/html"},
    {".ico",        "image/vnd.microsoft.icon"},
    {".ics",        "text/calendar"},
    {".jar",        "application/java-archive"},
    {".jpeg",       "image/jpeg"},
    {".jpg",        "image/jpeg"},
    {".js",         "text/javascript"},
    {".json",       "application/json"},
    {".jsonld",     "application/ld+json"},
    {".mid",        "audio/midi"},
    {".midi",       "audio/x-midi"},
    {".mjs",        "text/javascript"},
    {".mp3",        "audio/mpeg"},
    {".mpeg",       "video/mpeg"},
    {".mpkg",       "application/vnd.apple.installer+xml"},
    {".odp",        "application/vnd.oasis.opendocument.presentation"},
    {".ods",        "application/vnd.oasis.opendocument.spreadsheet"},
    {".odt",        "application/vnd.oasis.opendocument.text"},
    {".oga",        "audio/ogg"},
    {".ogv",        "video/ogg"},
    {".ogx",        "application/ogg"},
    {".otf",        "font/otf"},
    {".png",        "image/png"},
    {".pdf",        "application/pdf"},
    {".ppt",        "application/vnd.ms-powerpoint"},
    {".pptx",       "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
    {".rar",        "application/x-rar-compressed"},
    {".rtf",        "application/rtf"},
    {".sh",         "application/x-sh"},
    {".svg",        "image/svg+xml"},
    {".swf",        "application/x-shockwave-flash"},
    {".tar",        "application/x-tar"},
    {".tif",        "image/tiff"},
    {".tiff",       "image/tiff"},
    {".ttf",        "font/ttf"},
    {".txt",        "text/plain"},
    {".vsd",        "application/vnd.visio"},
    {".wav",        "audio/wav"},
    {".weba",       "audio/webm"},
    {".webm",       "video/webm"},
    {".webp",       "image/webp"},
    {".woff",       "font/woff"},
    {".woff2",      "font/woff2"},
    {".xhtml",      "application/xhtml+xml"},
    {".xls",        "application/vnd.ms-excel"},
    {".xlsx",       "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
    {".xml",        "application/xml"},
    {".xul",        "application/vnd.mozilla.xul+xml"},
    {".zip",        "application/zip"},
    {".3gp",        "video/3gpp"},
    {".3g2",        "video/3gpp2"},
    {".7z",         "application/x-7z-compressed"}
};

class Util
{
public:
    static void Split(const std::string &src, const std::string &sep, std::vector<std::string> *strs) // 字符串分割函数
    {
        size_t cur = 0, pos = 0;
        while (cur < src.size() && pos < src.size())
        {
            pos = src.find(sep, cur); // 从cur位置开始去查找sep子串
            if (pos != cur)
            {
                strs->push_back(src.substr(cur, pos - cur));
                cur = pos + sep.size();
            }
            else cur++;
        }
    }
    static bool ReadFile(const std::string &filename, std::string *buf) // 读取服务器上的文件里的所有内容，将读取到的内容放到buf里面
    {
        /* 为了保证数据读取上来后完全一致，我们采用二进制的读取和解析方式，在解析比特位的时候，不会按照ascll码8个比特位为一个字节的方式进行解析
        而是使用二进制的方式来进行解析。
        一般对于文本文件来说都是使用定长编码方式的方式，例如ASCII编码、UNICODE编码，ASCII编码采用8个特定的比特来表示每一个字符，
        而UNICODE编码采用16比特。
        对于二进制文件来说，使用的是自定义编码，也就是不定长度的编码，由你来决定多少个比特表示一个什么值，在以二进制方式来读取文件时，解码
        的方式同样为不定长度的解码，也就是说二进制编码写入文件的时候是怎样定义的，那么二进制解码读取文件的时候就以同样的方式进行解析 */

        std::ifstream ifs(filename.c_str(), std::ios::binary); // 在读取文件这里按照二进制方式进行原生读取
        if (ifs.is_open() == false)
        {
            ELOG("OPEN %s FILE FAILED!", filename.c_str());
            return false;
        }
        size_t fsize = 0;
        ifs.seekg(0, ifs.end);
        fsize = ifs.tellg(); // 从末尾偏移量获取当前流操作的位置，这个下标刚好就是文件的大小
        ifs.seekg(0, ifs.beg); // 重新将文件流操作的位置移动到开始位置，然后开始进行文件的读取操作
        buf->resize(fsize);
        ifs.read(&(*buf)[0], fsize);
        if (ifs.good() == false)
        {
            ELOG("READ %s CONTENT FAILED!", filename.c_str());
            return false;
        }
        return true;
    }
    static bool WriteFile(const std::string &filename, const std::string &buf)
    // 向服务器上写入文件内容，客户端想修改服务器上的某个文件内容，或者想向服务器内部写入一些文件
    {
        std::ofstream ofs(filename.c_str(), std::ofstream::binary | std::ofstream::trunc); // 对原有内容直接进行覆盖
        if (ofs.is_open() == false)
        {
            ELOG("OPEN %s FILE FAILED!", filename.c_str());
            return false;
        }
        ofs.write(buf.c_str(), buf.size());
        if (ofs.good() == false)
        {
            ELOG("WRITE %s CONTENT FAILED!", filename.c_str());
            return false;
        }
        return true;
    }
    /* URL编码: 避免URL中的请求资源路径和查询字符串中的特殊字符与HTTP请求中的特殊含义字符发生冲突，而导致产生歧义
    编码格式，将特殊字符的ascll码值，转换为两个十六进制字符，前缀为%
    RFC3986文档规定，URL中只允许包含英文字母（a-zA-Z）、数字（0-9）、-_.~4个特殊字符以及所有保留字符: # $ % ^ & _ { } ~ \
    如果URL中包含了其他字符，例如空格、引号、中文等，那么可能会造成服务器或者浏览器的解析错误，或者导致URL的语义变化，所以我们需要进行编码
    W3C文档规定，查询字符串parameter中的空格，需要被转换为+号，请求资源路径并没有要求
    注意: url编码指的是我们提交的数据如果含有和url中的特殊字符的话，那此时就需要进行url编码，而url本身method /path?key=val&key=val version
    所存在的特殊字符? = & /并不会被进行编码，会原封不动的在url里面存在，编码的是我们自己提交的数据。 */
    static std::string UrlEncode(const std::string &url, bool convert_space_to_plus)//该接口在服务器内部并没有用到
    {
        std::string res;
        for (auto &c : url)
        {
            if (c == ' ' && convert_space_to_plus) res += '+';
            else if (c != '.' && c != '-' && c != '_' && c != '~' && !isalnum(c))
            {
                char tmp[4] = {0};                       //"%HH\0"，总共4个字符，所以开4个char空间大小
                snprintf(tmp, sizeof(tmp), "%%%02X", c); // 两个%号相当于输出一个%
                res += tmp;
            }
            else res += c;//特殊字符，保留字符，英文数字
        }
        return res;
    }
    static char Convert(char ch1, char ch2)
    {
        char num1, num2;
        if (ch1 >= '0' && ch1 <= '9')
            num1 = (ch1 - '0') << 4;
        else if (ch1 >= 'a' && ch1 <= 'z')
            num1 = (ch1 - 'a' + 10) << 4;
        else
            num1 = (ch1 - 'A' + 10) << 4;

        if (ch2 >= '0' && ch2 <= '9')
            num2 = ch2 - '0';
        else if (ch2 >= 'a' && ch2 <= 'z')
            num2 = ch2 - 'a' + 10;
        else
            num2 = ch2 - 'A' + 10;

        return num1 + num2;
    }
    static std::string UrlDecode(const std::string &url, bool convert_plus_to_space)
    {
        std::string res;
        // 遇到了%，则将紧随其后的两个字符转换为数字，第一个字符所对应的数字左移四位，第二个字符转为数字即可
        for (int i = 0; i < url.size(); i++)
        {
            if (url[i] == '+' && convert_plus_to_space) res += ' ';
            else if (url[i] == '%' && (i + 2 < url.size()))
            {
                res += Convert(url[i + 1], url[i + 2]);
                i += 2;
            }
            else res += url[i];//特殊字符，保留字符，英文数字
        }
        return res;
    }
    static std::string StatuDesc(int statu) // 获取响应状态码对应的描述信息
    {
        auto it = _statu_info.find(statu);
        if (it != _statu_info.end()) return it->second;
        return "Unknow";
    }
    static std::string MimeByExt(const std::string &filename) // 通过文件后缀名获取Mime互联网上所传输的内容类型
    {
        // a.b.txt  先获取文件扩展名
        size_t pos = filename.find_last_of('.');
        if (pos == std::string::npos) return "application/octet-stream";//没有找到文件扩展名就返回一个二进制流
        //根据扩展名，获取mime
        std::string ext = filename.substr(pos);//".xxx"截取的子串包括.点字符
        auto it = _mime_info.find(ext);
        if (it == _mime_info.end()) return "application/octet-stream";//有文件扩展名，但是这个文件扩展名_mime_info中没有存储相应的信息 
        return it->second;
    }
    static bool IsDirectory(const std::string &filename) // 判断一个文件是否是目录
    {
        // int stat(const char *path, struct stat *buf);传入一个文件名，获取这个文件的属性
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if(ret < 0) return false;
        return S_ISDIR(st.st_mode);
    }
    static bool IsRegular(const std::string &filename) // 判断一个文件是否是普通文件
    {
        // int stat(const char *path, struct stat *buf);传入一个文件名，获取这个文件的属性
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if(ret < 0) return false;
        return S_ISREG(st.st_mode);        
    }
    static bool IsValidPath(const std::string &reqpath) // 判断请求的资源路径是否有效
    {
        /*  /index.html前边的/叫做相对根目录，也就是服务器上的某个webroot子目录，客户端只能请求相对根目录下的资源！
            /../login像这样的请求资源路径就是不合理的，也就是无效的请求资源路径 */
        /* webroot根目录深度层次默认设置为1，如果在解析请求资源路径的过程中，level变成了0，那么就说明该请求资源路径一定是无效的.
        遇到了".."字符串，level就减1，遇到/"xxx"/两个/之间的字符串，level就加1，*/
        int level = 1;
        std::vector<std::string> subdir;
        Split(reqpath, "/", &subdir);
        for(auto &s : subdir)
        {
            if(s == "..") level--;
            else level++;

            if(level <= 0) return false;
        }
        return true;
    }   
};

class HttpRequest
{
public:
    std::string _method;
    std::string _path;
    std::string _version;
    std::string _body;
    std::smatch _matches;//保存请求行正则解析后的所有子串
    std::unordered_map<std::string, std::string> _headers;
    std::unordered_map<std::string, std::string> _params;//?后&字符间隔的查询字符串
public:
    HttpRequest() : _version("HTTP/1.1") {}
    void Reset()
    {
        _method = _path = _body = "";
        _version = "HTTP/1.1";
        std::smatch empty;
        _matches.swap(empty);
        _headers.clear();
        _params.clear();
    }
    void SetHeader(const std::string &key, const std::string &val) { _headers.insert(std::make_pair(key, val)); }
    bool HasHeader(const std::string &key)const 
    {
        auto it = _headers.find(key);
        if (it == _headers.end()) return false;
        return true;
    }
    std::string GetHeader(const std::string &key)const
    {
        auto it = _headers.find(key);
        if (it == _headers.end()) return "";
        return it->second;
    }
    void SetParam(const std::string &key, const std::string &val) { _params.insert(std::make_pair(key, val)); }
    bool HasParam(const std::string &key)const
    {
        auto it = _params.find(key);
        if (it == _params.end()) return false;
        return true;
    }
    std::string GetParam(const std::string &key)const
    {
        auto it = _params.find(key);
        if (it == _params.end()) return "";
        return it->second;
    }
    size_t BodyLength()const
    {
        //Content-Length: 123\r\n
        bool ret = HasHeader("Content-Length");
        if (ret == false) return 0;
        std::string len = GetHeader("Content-Length");
        return std::stol(len);
    }
    bool IsShortConn()const
    {
        if(HasHeader("Connection") && GetHeader("Connection") == "keep-alive") return false;
        return true;
    }
};

class HttpResponse
{
public:
    int _statu_code;
    bool _redirect_flag;
    std::string _redirect_url;
    std::string _body;
    std::unordered_map<std::string, std::string> _headers;
public:
    HttpResponse() : _redirect_flag(false), _statu_code(200) {}
    HttpResponse(int statu) : _redirect_flag(false), _statu_code(statu) {} 
    void Reset() 
    {
        _statu_code = 200;
        _redirect_flag = false;
        _body = _redirect_url = "";
        _headers.clear();
    }
    void SetHeader(const std::string &key, const std::string &val) { _headers.insert(std::make_pair(key, val)); }
    bool HasHeader(const std::string &key) const
    {
        auto it = _headers.find(key);
        if (it == _headers.end()) return false;
        return true;
    }
    std::string GetHeader(const std::string &key) const
    {
        auto it = _headers.find(key);
        if (it == _headers.end()) return "";
        return it->second;
    }
    void SetBody(const std::string &body, const std::string &type = "text/html") 
    { 
        _body = body; 
        SetHeader("Content-Type", type); 
    }
    void SetRedirect(const std::string &url, int statu_code = 302)//默认设置为临时重定向
    {
        _statu_code = statu_code;
        _redirect_flag = true;
        _redirect_url = url;
    }
    bool IsShortConn()const
    {
        if(HasHeader("Connection") && GetHeader("Connection") == "keep-alive") return false;
        return true;
    }
};

typedef enum { RECV_REQLINE, RECV_HEAD, RECV_BODY, RECV_OVER, RECV_ERROR } RecvStage;
#define MAX_LINE_SIZE 8192

class HttpReqCtx
{
    //上下文的主要作用就是接收和解析缓冲区中的数据，直到从缓冲区中的字节流里面拿出一个完整的HttpRequest
private:
    int _resp_code;
    RecvStage _recv_stage;
    HttpRequest _request;
private:
    void RecvReqLine(Buffer *inbuf)
    {
        if(_recv_stage != RECV_REQLINE) return;
        //获取一行数据，通过调用buf->GetLineAndMoveIdx()就可以实现。但是要考虑一些特殊的因素.
        //1.如果缓冲区中的数据不足一行怎么办？ 2.如果缓冲区中的数据已经有特别特别多了，比如已经10MB多了，但是此时还没有\n字符，那这就是有问题的
        std::string line = inbuf->GetLineAndMoveIdx();//获取的一行数据是包含末尾的换行的！
        if(line.size() == 0)
        {
            if(inbuf->ReadableSize() > MAX_LINE_SIZE)
            {
                _recv_stage = RECV_ERROR;
                _resp_code = 414;//URI Too Long
                return;
            }
            return;//说明缓冲区中的数据不足一行，则继续等待新数据的到来，不更新接收状态
        }
        if(line.size() > MAX_LINE_SIZE)//虽然接收缓冲区中的数据确实满足了一行，但是这一行的字节大小太大了，这样的情况我也认为他是有问题的
        {
            _recv_stage = RECV_ERROR;
            _resp_code = 414;//URI Too Long
            return;
        }
        _recv_stage = RECV_HEAD;
        return ParseReqLine(line);
    }
    void ParseReqLine(const std::string &line)
    {
        std::smatch matches;
        std::regex exp("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
        bool ret = std::regex_match(line, matches, exp);
        if (ret == false)
        {
            _recv_stage = RECV_ERROR;
            _resp_code = 400;//Bad Request
            return;
        }
        // 字符串为"GET /csdn/login?user=xiaoming&pass=123123 HTTP/1.1"时，regex匹配后的结果如下
        // 0 : GET /csdn/login?user=xiaoming&pass=123123 HTTP/1.1
        // 1 : GET
        // 2 : /csdn/login
        // 3 : user=xiaoming&pass=123123
        // 4 : HTTP/1.1
        _request._method = matches[1];
        std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);
        _request._path = Util::UrlDecode(matches[2], false);//资源路径的获取，需要进行url解码，但是不需要+转为空格的解码
        _request._version = matches[4]; 
        /* 请求行中查询字符串的解析 查询字符串的格式为 key=val&key=val&key=val&key=val..... */
        std::vector<std::string> arr;
        Util::Split(matches[3], "&", &arr);
        for(auto &str : arr)
        {
            std::vector<std::string> tmp;
            Util::Split(str, "=", &tmp);
            if(tmp.size() < 2)//tmp的大小如果不足2，那就说明你的这个查询字符串是有问题的
            {
                _recv_stage = RECV_ERROR;
                _resp_code = 400;//Bad Request
                return;
            } 
            _request.SetParam(Util::UrlDecode(tmp[0], true), Util::UrlDecode(tmp[1], true));
            //W3C文档规定，url编码时，查询字符串parameter中的空格，需要被转换为+号，请求资源路径并没有要求，所以UrlDecode第二个参数为true
        }
        return;
    }
    void RecvHead(Buffer *inbuf)
    {
        if(_recv_stage != RECV_HEAD) return;
        std::string line = inbuf->GetLineAndMoveIdx();
        while(line != "\n" && line != "\r\n")//读到空行为止，则头部字段读取完毕
        {
            if(line.size() == 0)
            {
                if(inbuf->ReadableSize() > MAX_LINE_SIZE)
                {
                    _recv_stage = RECV_ERROR;
                    _resp_code = 414; 
                    return;
                }
                return; 
            }
            if(line.size() > MAX_LINE_SIZE) 
            {
                _recv_stage = RECV_ERROR;
                _resp_code = 414; 
                return;
            }
            ParseHead(line);
            if(_resp_code >= 400) return;
            line = inbuf->GetLineAndMoveIdx();//继续从buf中获取下一行的数据
        }
        _recv_stage = RECV_BODY;
        return;
    }
    void ParseHead(std::string line)
    {
        if(line.back() == '\n') line.pop_back();
        if(line.back() == '\r') line.pop_back();
        std::vector<std::string> tmp;
        Util::Split(line, ": ", &tmp);
        if(tmp.size() < 2)//tmp的大小如果不足2，那就说明你的头部字段是有问题的
        {
            _recv_stage = RECV_ERROR;
            _resp_code = 400;//Bad Request
            return;
        } 
        _request.SetHeader(tmp[0], tmp[1]);
        return;
    }
    void RecvBody(Buffer *inbuf)
    {
        if(_recv_stage != RECV_BODY) return;
        size_t len = _request.BodyLength();
        if(len == 0)
        {
            _recv_stage = RECV_OVER;//没有正文，则请求解析完毕
            return;
        }
        //1.当前还需要接收多少正文数据
        size_t need_len = len - _request._body.size();
        //2.如果缓冲区中的数据大小超过正文长度，那取出所需要的大小的正文数据即可，多出来那部分数据就是下一个http请求的信息
        if(inbuf->ReadableSize() >= need_len)
        {
            _request._body += inbuf->ReadAndMoveIdx(need_len);
            _recv_stage = RECV_OVER;
            return;
        }
        //3.如果缓冲区中的数据不够当前正文长度的大小，那就说明服务器接收到的数据不足一条完整的数据，那就先把当前这点数据取出来，然后继续等待新的数据到来
        else
        {
            _request._body += inbuf->ReadAndMoveIdx(inbuf->ReadableSize());
            return;
            /* 这里不更改接收状态_recv_statu，因为我们正文的数据还没有读取完呢，服务器还需要继续等待数据的到来！
            值得多说一句的是，像客户端发送http请求，那么当这个请求发送到服务器时，其实数据会先被拷贝到socket内核接收缓冲区，然后我们服务器程序内部调用了
            recv system call来将数据拷贝到应用层缓冲区Buffer里面，然而socket的内核recv buffer和send buffer都是有他们的固定空间大小的，他不是无限制增长的
            所以，我们在recv的时候是有可能不能一次性把所有数据都拿完的，因为此时socket的内核接收缓冲区即使被打满了，但他还有可能没有收到完整的http请求数据，
            那就应该在上层程序员recv之后，清空socket内核接收缓冲区后，服务器的socket继续接收剩余的http请求数据，然后服务器的程序员再进行recv将剩余数据拿到Buffer
            里面，这样Buffer里面就又会新增一部分http请求数据，所以如果Buffer里面的正文数据不够完整的话，那我们就再等一等，等等socket内核缓冲区接收到剩余的数据
            ，然后Buffer将内核缓冲区的数据拿上来，拿上来之后，我们就可以继续进行处理http请求正文body的内容了。 */
        }
    }
public:
    HttpReqCtx() : _resp_code(200), _recv_stage(RECV_REQLINE) {}
    int GetRespCode() { return _resp_code; }
    RecvStage GetRecvStage() { return _recv_stage; }
    HttpRequest& GetWholeReq() { return _request; }
    void ParseHttpReq(Buffer *inbuf)
    {
        switch (_recv_stage)
        {
            //状态机：不同的处理阶段，执行不同的函数
            case RECV_REQLINE: RecvReqLine(inbuf);
            case RECV_HEAD: RecvHead(inbuf);
            case RECV_BODY: RecvBody(inbuf);
        }
    }
    void Reset()
    {
        _resp_code = 200, _recv_stage = RECV_REQLINE;
        _request.Reset();
    }
};

class HttpServer
{
private:
    using Business_Handle = std::function<void (const HttpRequest &, HttpResponse *)>; 
    // 业务处理函数
    using Handle_Set = std::vector<std::pair<std::regex, Business_Handle>>;
    // 特定请求方法的业务处理集合 std::pair<<请求资源路径的正则表达式, 函数指针>>
    Handle_Set _get_route; 
    Handle_Set _post_route; 
    Handle_Set _put_route; 
    Handle_Set _delete_route; 
    std::string _webroot; 
    TcpServer _server; 
private:
    bool IsStaticReq(HttpRequest &req)
    {
        //1.必须设置了静态资源的根目录
        if(_webroot.empty()) return false;
        //2.请求方法必须是GET或HEAD，这样获取实体web资源的HTTP请求方法
        if(req._method != "GET" && req._method != "HEAD") return false;
        //3.请求的资源路径必须是一个有效的合法路径
        if(Util::IsValidPath(req._path) == false) return false;
        //4.在请求资源路径合法的情况下，请求的资源文件也必须是存在的
        //  有一种请求是比较特殊的 只有目录，请求资源路径只有 "/" 或 "/image/"，这种纯粹的目录情况下我们会在 / 后面追加index.html也就是服务器的首页资源
        std::string path = _webroot + req._path;
        if(req._path.back() == '/') path += "index.html";
        //有可能有人会搞破坏，例如 /image//   转换为  ./wwwroot/image//index.html，对于一个路径里面有两个斜杠，
        //那他肯定不是一个正常的普通文件，则IsRegular返回false
        if(Util::IsRegular(path) == false) return false;
        return true;
    }
    void HandleStaticReq(const HttpRequest &req, HttpResponse *resp)//处理静态资源的请求，将静态资源文件的数据读取出来，放到resp的body正文中
    {
        std::string path = _webroot + req._path;
        if(path.back() == '/') path += "index.html";

        //1.在判断是否为静态资源时，如果判断成功，则req对象中的_path已经被改为服务器上实际静态资源的路径了，这里直接读取即可
        bool ret = Util::ReadFile(path, &resp->_body);//[] () . ->
        if(ret == false) return;
        std::string mime = Util::MimeByExt(path);
        resp->SetHeader("Content-Type", mime);
    }
    void HandleDynamicReq(HttpRequest &req, HttpResponse *resp, Handle_Set route_table)
    {
        //在对应的请求方法的路由表中，查找是否有对应请求的处理函数，如果有则进行调用，如果没有则说明客户端的这种请求在服务器上是找不到的，返回404 
        /*处理思想:
        路由表中的key存储的是对应请求资源路径解析时的一个正则表达式，那就说明，只要当前req的url中的资源路径能和路由表中的rgx进行正则匹配，
        那就说明，这个请求在服务器的某个路由表中有对应匹配的处理函数 */
        // /numbers/12345     <"/numbers/(\\d+)", Handler>    
        // regex解释-->C++这一层看到\字符就会进行转义，所以传给regex引擎的就是去掉一个\后的\d+，在regex引擎里面\d+会被解释为匹配数字字符一次或多次
        for(auto &p : route_table)
        {
            std::regex rgx(p.first);
            bool ret = std::regex_match(req._path, req._matches, rgx);
            if(ret)
            {
                p.second(req, resp);//正则匹配成功，传入请求信息和空的resp，执行对应的业务处理函数
                return;
            }
        }
        resp->_statu_code = 404;//not found
    } 
    /* HEAD方法与 GET 方法类似，也是请求从服务器获取资源，服务器的处理机制也是一样的，但服务器不会返回请求的实体数据，只会传回响应头，也就是资源的“元信息”。 
    HEAD 方法可以看做是 GET 方法的一个“简化版”或者“轻量版”。因为它的响应头与 GET 完全相同，所以可以用在很多并不真正需要资源的场合，避免传输 body 数据的浪费。
    比如，想要检查一个文件是否存在，只要发个 HEAD 请求就可以了，没有必要用 GET 把整个文件都取下来。再比如，要检查文件是否有最新版本，
    同样也应该用 HEAD，服务器会在响应头里把文件的修改时间传回来。 */   
    void RouteReq(HttpRequest &req, HttpResponse *resp)
    {
        //1.对请求进行分类，是一个静态资源请求还是动态功能性请求(GET和HEAD既有可能是静态请求，也有可能是动态请求)
        //  如果既不是静态资源请求，也没有响应的动态功能请求的处理函数，那就返回404，表示请求是不存在的，是未知的，服务器找不到你所请求的资源
        //2.GET请求也有可能是动态功能性请求，像百度你在进行搜索的时候，其实就是GET请求获取百度服务器中存储的关键字相对应的一系列web资源，而对关键字进行检索
        //  这其实就是一个动态的功能性请求
        if (IsStaticReq(req)) return HandleStaticReq(req, resp);
        if (req._method == "GET" || req._method == "HEAD") return HandleDynamicReq(req, resp, _get_route);
        if (req._method == "POST") return HandleDynamicReq(req, resp, _post_route);
        if (req._method == "PUT") return HandleDynamicReq(req, resp, _put_route);
        if (req._method == "DELETE") return HandleDynamicReq(req, resp, _delete_route);
        resp->_statu_code = 405;//Method Not Allowed (请求方法不被服务器允许)
    }
    void SendResp(const ConnPtr &conn, const HttpRequest &req, HttpResponse *resp)
    {
        //1.完善resp的头部字段
        if(req.IsShortConn()) resp->SetHeader("Connection", "close");
        else resp->SetHeader("Connection", "keep-alive");

        if(!resp->_body.empty() && !resp->HasHeader("Content-Length")) resp->SetHeader("Content-Length", std::to_string(resp->_body.size()));
        if(!resp->_body.empty() && !resp->HasHeader("Content-Type")) resp->SetHeader("Content-Type", "application/octet-stream");//二进制流类型
        if(resp->_redirect_flag) resp->SetHeader("Location", resp->_redirect_url);
        //2.将resp对象中的成员组织成http响应格式的字符串
        std::string resp_str;
        resp_str += req._version + " " + std::to_string(resp->_statu_code) + " " + Util::StatuDesc(resp->_statu_code) + "\r\n";
        for(auto &head : resp->_headers)
        {
            resp_str += head.first + ": " + head.second + "\r\n";
        }
        resp_str += "\r\n";
        resp_str += resp->_body;
        //3.将组织好的字符串发送给客户端（其实是发送到conn的_out_buffer里面，实际的发送还要等待事件驱动处理）
        conn->Send(resp_str.c_str(), resp_str.size());
    }
    void OnEstablished(const ConnPtr &conn) 
    {
        conn->SetContext(HttpReqCtx());//为刚刚建立好的连接设置一个HttpReqCtx类型的上下文信息，则conn的any容器中就会存储HttpReqCtx类型的请求上下文信息 
        DLOG("NEW CONNECTION %p", conn.get());
    }
    void OnError(HttpResponse *resp)
    {
        std::string body;
        body += "<html><head><meta http-equiv='Content-Type' content='text/html;charset=utf-8'></head><body><h1>";
        body += std::to_string(resp->_statu_code) + " " + Util::StatuDesc(resp->_statu_code) + "</h1></body></html>";
        resp->SetBody(body, "text/html");
    }
    void OnMessage(const ConnPtr &conn, Buffer *inbuf)//对于Buffer缓冲区中接收到的数据先进行解析，然后再处理
    {
        while(inbuf->ReadableSize() > 0)
        {
            //1.获取http请求上下文，对服务器缓冲区中收到的数据进行解析，解析后获得一个完整的HttpRequest对象
            HttpReqCtx *context = conn->GetContext()->get<HttpReqCtx>();
            /* GetContext返回的是一个Any对象的指针，我们要的是Any里面保存的数据对象的类型的指针，所以调用一下get接口，注意get是一个模板函数，调用时要指定类型 */
            context->ParseHttpReq(inbuf);
            HttpResponse resp(context->GetRespCode());
            //如果缓冲区中的数据解析出错，那就直接组织一个错误的响应信息进行返回，状态码在解析http请求出错的时候就会设置好
            if(context->GetRespCode() >= 400)
            {
                OnError(&resp);
                SendResp(conn, HttpRequest(), &resp);
                inbuf->Clear();
                //缓冲区中的数据都解析错误了，说明这些数据都已经不符合http协议了，服务器那就没有必要再对数据进行解析了，直接清空这些非HTTP协议的数据即可
                context->Reset();//在关闭连接之前，一定要将上下文进行重置，将上下文信息恢复到最初的状态，避免影响后续对缓冲区中数据的解析处理
                conn->Shutdown();
                return;
            }
            if(context->GetRecvStage() != RECV_OVER) return;//当前请求还没有接受完整，则继续等待新数据到来之后再进行处理

            //---走到这里，说明缓冲区中的数据已经解析完毕了，那么此时请求上下文信息中就会包含一个完整的HttpRequest对象
            HttpRequest& req = context->GetWholeReq();//只要connection没销毁，那么context就不会销毁，自然whole HttpReq就不会销毁，&返回
            //2.对HttpRequest对象进行一个请求路由，去查找这个请求相对应的业务处理函数
            RouteReq(req, &resp);
            //3.组织发送HttpResponse
            SendResp(conn, req, &resp);
            //4.处理完毕一整条请求过后，需要重置一下当前连接的上下文，这样就不会让之前的上下文影响到下一次连接处理http请求了
            context->Reset();
            //5.通过当前http的长短连接来判断是否要关闭当前连接，还是继续进行数据通信和处理
            if(resp.IsShortConn())
            {
                //如果是短连接，那就只进行一次业务逻辑处理，处理完毕之后就关闭掉连接，然后退出循环，结束OnMessage的函数执行
                conn->Shutdown();
                break;
            } 
        }
    }
public:
    HttpServer(int port, int timeout = DEFAULT_TIMEOUT) : _server(port)
    {
        _server.OpenInactiveRelease(timeout);//默认开启连接的非活跃释放
        //只设置连接建立回调和业务逻辑处理回调
        _server.SetEstablishedCallBack(std::bind(&HttpServer::OnEstablished, this, std::placeholders::_1));
        _server.SetMessageCallBack(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
    }
    void SetWebroot(const std::string &path)
    {
        bool ret = Util::IsDirectory(path);
        assert(ret == true);
        _webroot = path;
    }
    void AddGetRoute(std::string RegExp, const Business_Handle &f) { _get_route.push_back(std::make_pair(std::regex(RegExp), f)); }
    void AddPostRoute(std::string RegExp, const Business_Handle &f) { _post_route.push_back(std::make_pair(std::regex(RegExp), f)); }
    void AddPutRoute(std::string RegExp, const Business_Handle &f) { _put_route.push_back(std::make_pair(std::regex(RegExp), f)); }
    void AddDeleteRoute(std::string RegExp, const Business_Handle &f) { _delete_route.push_back(std::make_pair(std::regex(RegExp), f)); }
    void SetThreadCount(int count) { _server.SetThreadCount(count); }
    void Run() { _server.Run(); }
};
